﻿Imports System.IO
Imports System.Text
Imports System.Threading

''' <summary>
''' Provides some utilities for I/O operations
''' </summary>
''' <remarks></remarks>
Public Module IOUtilities
    ''' <summary>
    ''' Asyncronously get the size of a folder in the correct multiplier 
    ''' </summary>
    ''' <param name="path">The folder path as string</param>
    ''' <returns>Returns null if any exception occurred</returns>
    ''' <remarks></remarks>
    Public Async Function GetFolderSize(path As String, Optional token As CancellationToken = Nothing, Optional pauseToken As PauseToken = Nothing) As Task(Of String)
        ' Initialize a new string that will hold the size label
        Dim sizeLabel As String = Nothing

        ' Create a new Long to hold the size numeric value
        Dim size As Long = 0

        Try
            ' Create a new DirectoryInfo from the argument passed
            Dim folder As New DirectoryInfo(path)

            ' Process folder
            Await Task.Run(Async Function()
                               ' Enumerate all files of the directory as FileInfo
                               Dim files As FileInfo() = folder.GetFiles("*.*", SearchOption.AllDirectories)

                               ' Sum each file size
                               For Each file As FileInfo In files
                                   ' Throw the cancellation request if the token has one and is not nothing
                                   If Not token = Nothing Then
                                       token.ThrowIfCancellationRequested()
                                   End If
                                   If Not pauseToken.Equals(Nothing) Then
                                       Await pauseToken.WaitWhilePausedAsync()
                                   End If

                                   size += file.Length
                               Next
                           End Function)

            ' Convert the size to the best multiplier
            'sizeLabel = Await GetSizeMultiplier(size)

            sizeLabel = size
        Catch ac As UnauthorizedAccessException
            Return "Access denied"
        Catch ex As Exception
            Return String.Empty
        End Try

        Return sizeLabel
    End Function

    ''' <summary>
    ''' Asyncronously get the right multiplier of the given size <example>(i.e.: size given is 2048 KB, returns 2 MB)</example>
    ''' </summary>
    ''' <param name="size">The original size</param>
    ''' <returns>A string containing the right size multiplier rounded at the second digit.
    ''' Returns null if any exception occurred</returns>
    ''' <remarks>The inverted function is <see cref="GetOriginalSize">GetOriginalSize</see></remarks>
    Public Async Function GetSizeMultiplier(size As Long) As Task(Of String)
        ' Initialize a new string that will hold the size label
        Dim sizeLabel As String = Nothing

        Try
            Await Task.Run(Sub()
                               If size <= 1023 Then
                                   sizeLabel = size.ToString() & " bytes"
                               ElseIf size <= 1024 ^ 2 Then
                                   sizeLabel = Math.Round(size / 1024, 2) & " KB"
                               ElseIf size <= 1024 ^ 3 Then
                                   sizeLabel = Math.Round(size / 1024 ^ 2, 2) & " MB"
                               ElseIf size <= 1024 ^ 4 Then
                                   sizeLabel = Math.Round(size / 1024 ^ 3, 2) & " GB"
                               ElseIf size <= 1024 ^ 5 Then
                                   sizeLabel = Math.Round(size / 1024 ^ 4, 2) & " TB"
                               ElseIf size <= 1024 ^ 6 Then
                                   sizeLabel = Math.Round(size / 1024 ^ 5, 2) & " PB"
                               End If
                           End Sub)
        Catch ex As Exception
            Return String.Empty
        End Try

        Return sizeLabel
    End Function

    ''' <summary>
    ''' Asyncronously get the original size of a formatted string <example>(i.e.: 1024 KB, returns 1048576)</example> 
    ''' </summary>
    ''' <param name="sizeLabel">The formatted size as string.</param>
    ''' <returns>A long containing the orignal size
    ''' Returns null if any exception occurred</returns>
    ''' <remarks>The inverted function of <see cref="GetSizeMultiplier">GetSizeMultiplier</see></remarks>
    Public Async Function GetOriginalSize(sizeLabel As String) As Task(Of Long)
        ' Initialize a long that will hold the original size
        Dim size As Long = 0

        Try
            Await Task.Run(Sub()
                               ' Restore the original size from the given string
                               If sizeLabel.Contains("bytes") Then
                                   size = sizeLabel.Remove(sizeLabel.IndexOf(" "), sizeLabel.Length - sizeLabel.IndexOf(" "))
                               ElseIf sizeLabel.Contains("KB") Then
                                   size = CLng(sizeLabel.Remove(sizeLabel.IndexOf(" "), sizeLabel.Length - sizeLabel.IndexOf(" "))) * 1024
                               ElseIf sizeLabel.Contains("MB") Then
                                   size = CLng(sizeLabel.Remove(sizeLabel.IndexOf(" "), sizeLabel.Length - sizeLabel.IndexOf(" "))) * (1024 ^ 2)
                               ElseIf sizeLabel.Contains("GB") Then
                                   size = CLng(sizeLabel.Remove(sizeLabel.IndexOf(" "), sizeLabel.Length - sizeLabel.IndexOf(" "))) * (1024 ^ 3)
                               ElseIf sizeLabel.Contains("TB") Then
                                   size = CLng(sizeLabel.Remove(sizeLabel.IndexOf(" "), sizeLabel.Length - sizeLabel.IndexOf(" "))) * (1024 ^ 4)
                               ElseIf sizeLabel.Contains("PB") Then
                                   size = CLng(sizeLabel.Remove(sizeLabel.IndexOf(" "), sizeLabel.Length - sizeLabel.IndexOf(" "))) * (1024 ^ 5)
                               End If
                           End Sub)
        Catch ex As Exception
            Return Nothing
        End Try

        Return size
    End Function

    ''' <summary>
    ''' Delete every file and subfolder of a folder
    ''' </summary>
    ''' <param name="path">The path of the root folder</param>
    ''' <param name="exceptionList">The StringBuilder that will hold the paths of the files and subfolders
    ''' that couldn't be deleted</param>
    ''' <returns>A StringBuilder containing the paths of the files and subfolders that couldn't be deleted</returns>
    ''' <remarks>When cleaning is done, show a MessageBox with the exceptionList</remarks>
    Public Async Function ClearFolderContent(path As String, exceptionList As StringBuilder, token As CancellationToken, pauseToken As PauseToken) As Task(Of StringBuilder)
        ' Process folder's files
        ' Search into every subfolder
        For Each foundFile As FileInfo In New DirectoryInfo(path).GetFiles("*.*", SearchOption.AllDirectories)
            ' Throw the cancellation request if the token has one
            token.ThrowIfCancellationRequested()
            Await pauseToken.WaitWhilePausedAsync()

            Try
                ' Delete the file asyncronously
                Using gutmannClass As New GutmannAlgorithm(foundFile.FullName)
                    Await gutmannClass.GutmannSecureDelete(token, pauseToken)
                End Using

                Await Task.Run(Async Function()
                                   ' Throw the cancellation request if the token has one
                                   token.ThrowIfCancellationRequested()
                                   Await pauseToken.WaitWhilePausedAsync()

                                   ' Check if the file goes to the recycle or is permanently deleted
                                   If AppSettings.ErrorDetected Or AppSettings.OverwriteMethod = 0 Then
                                       foundFile.Delete()
                                   Else
                                       My.Computer.FileSystem.DeleteFile(foundFile.FullName, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin)
                                   End If

                               End Function, token)
            Catch ex As Exception
                ' If the file couldn't be deleted, add its path to the exceptionList
                If Not exceptionList.ToString().Contains(foundFile.FullName) Then
                    exceptionList.AppendLine(foundFile.FullName)
                End If
            End Try
        Next

        ' Process folder's subfolders
        For Each foundFolder As DirectoryInfo In New DirectoryInfo(path).GetDirectories
            ' Throw the cancellation request if the token has one
            token.ThrowIfCancellationRequested()
            Await pauseToken.WaitWhilePausedAsync()

            Try
                ' Delete the folder asyncronously and recursively
                Await Task.Run(Async Function()
                                   ' Throw the cancellation request if the token has one
                                   token.ThrowIfCancellationRequested()
                                   Await pauseToken.WaitWhilePausedAsync()

                                   ' Check if the folder goes to the recycle or is permanently deleted
                                   If AppSettings.ErrorDetected Or AppSettings.OverwriteMethod = 0 Then
                                       foundFolder.Delete(True)
                                   Else
                                       My.Computer.FileSystem.DeleteDirectory(foundFolder.FullName, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin)
                                   End If
                               End Function, token)
            Catch ex As Exception
                ' If the folder couldn't be deleted, add its path to the exceptionList
                If Not exceptionList.ToString.Contains(foundFolder.FullName) Then
                    exceptionList.AppendLine(foundFolder.FullName)
                End If
            End Try
        Next

        Return exceptionList
    End Function
End Module
